#include "XmlLoader.h"
#include "../Graphics/GraphicsLayer.h"

using namespace std;

cXmlLoader::cXmlLoader(char* filename)
{
    this->ParseXML(filename);
}

cXmlLoader::~cXmlLoader(){
    // Vaciar el contenido de la clase de parseo de XML
    // ID'S
    this->m_pCordIdList.clear();
    this->m_pColorsIdList.clear();
    this->m_pTextCoordsIdList.clear();
    // VALUES
    this->m_pVertices.clear();
    this->m_pColors.clear();
    this->m_pNormals.clear();
    this->m_pTexCoords.clear();
    this->m_pTexturesNamesList.clear();
    
    // Actualmente no se está usando
    this->m_pMatIdsList.clear();
    
    this->m_pModelData.clear();
}

int cXmlLoader::Mcount(char S[SIZE], char c) {
    {
        int i = 0, count = 0;
        for (; S[i] != 0; ++i)
            if (S[i] == c) ++count;
        return count;
    }
}

float cXmlLoader::Matof(std::string ss) {
    char* is = (char*) ss.c_str();
    char* s = (char*) ss.c_str();
    float num = 0.0;
    float kon = 0.0;
    int flag, flag2, i;
    float exp;
    while (*s) {
        if (*s >= '0' && *s <= '9') {
            num = 10.0 * num + (float) (*s - '0');
            kon *= 10.0;
        } else if (*s == '.') kon = 1.0;
        else if (*s == 'e') {
            break;
        }
        s++;
    }
    for (i = 1; *(s + i); i++)
        exp = exp * 10 + (*(s + i) - '0');
    flag = this->Mcount(is, 'e');
    if (flag != 0) {
        num = num / (kon == 0.0 ? 1.0 : kon);
        num = num * pow(10, exp);
    } else {
        num = num / (kon == 0.0 ? 1.0 : kon);
    }
    flag2 = this->Mcount(is, '-');
    if (flag2 != 0) {
        num = num * (-1);
    }    
    return num;
}


void cXmlLoader::TokenizeXmlValueToInt(vector<GLuint>* pList, xmlDocPtr doc, xmlNodePtr cur)
{
    int cantidad = this->GetCountFromXml(cur);
        
    pList->reserve(cantidad);
    
    char* key = (char*) xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
    std::string m_pwszValue = (std::string) key;
    free(key);
    
    const char* sep = " \t\n\r";

    unsigned int tokStart = m_pwszValue.find_first_not_of(sep, 0);
    unsigned int tokEnd;

    while (tokStart != -1) {
        tokEnd = m_pwszValue.find_first_of(sep, tokStart);
        
        std::string sbStr;
        sbStr = m_pwszValue.substr(tokStart, tokEnd - tokStart).c_str();
        
        GLuint token = atoi((const char*) sbStr.c_str());
        pList->push_back(token);
        
        tokStart = m_pwszValue.find_first_not_of(sep, tokEnd);
    }
}

void cXmlLoader::TokenizeXmlValueToFloat(vector<GLfloat>* pList, xmlDocPtr doc, xmlNodePtr cur)
{
    int cantidad = this->GetCountFromXml(cur);    
    
    pList->reserve(cantidad);
    
    char* key = (char*) xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
    std::string m_pwszValue = (std::string) key;
    free(key);
    
    const char* sep = " \t\n\r";

    unsigned int tokStart = m_pwszValue.find_first_not_of(sep, 0);
    unsigned int tokEnd;

    while (tokStart != -1) {
        tokEnd = m_pwszValue.find_first_of(sep, tokStart);
        
        std::string sbStr;
        sbStr = m_pwszValue.substr(tokStart, tokEnd - tokStart).c_str();
        
        GLfloat token = (GLfloat) Matof(sbStr);
        
        pList->push_back(token);
        
        tokStart = m_pwszValue.find_first_not_of(sep, tokEnd);
    }
}

std::string cXmlLoader::GetPropFromXml(xmlNodePtr cur, const char* name)
{
    xmlChar* xmlValue;
    xmlValue = xmlGetProp(cur, (const xmlChar*) name);
    return (char *) xmlValue;
}

int cXmlLoader::GetCountFromXml(xmlNodePtr cur)
{
    std::string cantidadCharMod;

    cantidadCharMod = this->GetPropFromXml(cur, "count");
    return atoi((char *) cantidadCharMod.c_str());
}

void cXmlLoader::ParseMaterial(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur = cur->xmlChildrenNode;
    
    while (cur != NULL) {
        // Si no es text
        if ((xmlStrcmp(cur->name, (const xmlChar *) "text"))) {
            // Ambient
            if ((!xmlStrcmp(cur->name, (const xmlChar *) "ambient"))) {
                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                
                xmlFree(key);
            }
            // Diffuse
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "diffuse"))) {
                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                
                xmlFree(key);
            }
            // Specular
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "specular"))) {
                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                
                xmlFree(key);
            }
            // Opacity
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "opacity"))) {
                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                
                xmlFree(key);
            }
            // Bitmap
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "bitmap"))) {
                std::string textureName;
                xmlChar* key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                textureName = (char *) key;
                this->m_pTexturesNamesList.push_back(textureName);
                xmlFree(key);
            }
        }

        cur = cur->next;
    }
    return;
}

void cXmlLoader::ParseMesh(xmlDocPtr doc, xmlNodePtr cur)
{
    xmlChar *key;
    cur = cur->xmlChildrenNode;
    
    while (cur != NULL) {
        // Si no es text
        if ((xmlStrcmp(cur->name, (const xmlChar *) "text"))) {
            // Boundig box
            if ((!xmlStrcmp(cur->name, (const xmlChar *) "boundingBox"))) {
                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                
                xmlFree(key);
            }
            // Coordinates Index's
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "coordinatesIdx"))) {
                this->TokenizeXmlValueToInt(&this->m_pCordIdList, doc, cur);
            }
            // Textures Coordinates Index's
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "textCoordsIdx"))) {
                this->TokenizeXmlValueToInt(&this->m_pTextCoordsIdList, doc, cur);
            }
                // Colors Index's
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "colorsIdx"))) {
                this->TokenizeXmlValueToInt(&this->m_pColorsIdList, doc, cur);
            }
            // Matireals ID's
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "matIds"))) {
                this->TokenizeXmlValueToInt(&this->m_pMatIdsList, doc, cur);
            }
            // Vertices
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "vertices"))) {
                this->TokenizeXmlValueToFloat(&this->m_pVertices, doc, cur);
                int total = this->m_pVertices.size();
                int i = 0;
                for(i=2; i<total; i+=3){
                    this->m_pVertices[i] *= -1;
                }
            }
            // Normals
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "normals"))) {
                this->TokenizeXmlValueToFloat(&this->m_pNormals, doc, cur);
                int total = this->m_pNormals.size();
                int i = 0;
                for(i=2; i<total; i+=3){
                    this->m_pNormals[i] *= -1;
                }
            }
            // texCoords
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "texCoords"))) {
                this->TokenizeXmlValueToFloat(&this->m_pTexCoords, doc, cur);
//                int total = this->m_pTexCoords.size();
//                int i = 0;
//                for(i=2; i<total; i+=2){
//                    this->m_pTexCoords[i] *= -1;
//                }
            }
            // Colors
            else if ((!xmlStrcmp(cur->name, (const xmlChar *) "colors"))) {
                this->TokenizeXmlValueToFloat(&this->m_pColors, doc, cur);
            }
        }
        
        cur = cur->next;
    }
            
    this->LoadModelData();
        
    return;
}

void cXmlLoader::ParseMaterials(xmlDocPtr doc, xmlNodePtr cur)
{
    int cantidad = this->GetCountFromXml(cur);
    
    // Recorrer cada uno de los meshes
    cur = cur->xmlChildrenNode;
    int contador = 0;
    this->m_pTexturesNamesList.reserve(cantidad);
    while (contador < cantidad) {
        if ((!xmlStrcmp(cur->name, (const xmlChar *) "m"))) {
            this->ParseMaterial(doc, cur);
            contador++;
        }
        cur = cur->next;
    }

    return;
}

void cXmlLoader::ParseMeshes(xmlDocPtr doc, xmlNodePtr cur)
{
    int cantidad = this->GetCountFromXml(cur);
    
    // Recorrer cada uno de los meshes
    cur = cur->xmlChildrenNode;
    int contador = 0;
    // Se reserva la cantidad de meshes en el vector de meshes
    this->m_pModelData.reserve(cantidad);
    while (contador < cantidad) {
        if(!xmlStrcmp(cur->name, (const xmlChar *) "mesh")){
            std::string matId;
            matId = this->GetPropFromXml(cur, "matId");
            this->matID = atoi((char *) matId.c_str());
            this->name = this->GetPropFromXml(cur, "name");
                       
            this->ParseMesh(doc, cur);
            contador++;
        }
        cur = cur->next;
    }

    return;
}

void cXmlLoader::ParseXML(char *docname) {
    xmlDocPtr doc;
    xmlNodePtr cur;
    doc = xmlParseFile(docname);
    if (doc == NULL) {
        fprintf(stderr, "Document not parsed successfully. \n");
        return;
    }
    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        fprintf(stderr, "empty document\n");
        xmlFreeDoc(doc);
        return;
    }
    if (xmlStrcmp(cur->name, (const xmlChar *) "tgcScene")) {
        fprintf(stderr, "document of the wrong type, root node != tgcScene");
        xmlFreeDoc(doc);
        return;
    }

    cur = cur->xmlChildrenNode;
    int earlyOut = 0;

    while (cur != NULL && earlyOut < 2) {
        if ((!xmlStrcmp(cur->name, (const xmlChar *) "materials"))) {
            this->ParseMaterials(doc, cur);
            earlyOut ++;
        }
        
        if ((!xmlStrcmp(cur->name, (const xmlChar *) "meshes"))) {
            this->ParseMeshes(doc, cur);
            
            int i = 0;
            // Convertir coordenada v de DirectX a OpenGL (v = 1 - v)
            for (i = 1; i < this->m_pTexCoords.size(); i += 2) {
                this->m_pTexCoords[i] = 1 - this->m_pTexCoords[i];
            }
            
            earlyOut ++;
        }
        
        cur = cur->next;
    }
    
    xmlFreeDoc(doc);    
    xmlCleanupParser();
    
    Graphics()->m_pTexturesNamesList.swap(this->m_pTexturesNamesList);
    this->m_pTexturesNamesList.clear();
    
    return;
}

void cXmlLoader::LoadModelData()
{
    modelData data;
    
    data.matID = this->matID;
    data.name = this->name;
    
    int size = this->m_pCordIdList.size();
    data.m_pCordIdList.reserve(size);
    size = this->m_pTextCoordsIdList.size();
    data.m_pTextCoordsIdList.reserve(size);
    size = this->m_pColorsIdList.size();
    data.m_pColorsIdList.reserve(size);
    size = this->m_pMatIdsList.size();
    data.m_pMatIdsList.reserve(size);
    size = this->m_pVertices.size();
    data.m_pVertices.reserve(size);
    size = this->m_pNormals.size();
    data.m_pNormals.reserve(size);
    size = this->m_pColors.size();
    data.m_pColors.reserve(size);
    size = this->m_pTexCoords.size();
    data.m_pTexCoords.reserve(size);
    size = this->m_pTexturesNamesList.size();
    Graphics()->m_pTexturesNamesList.reserve(size);
    Graphics()->m_pTexturesPointers.reserve(size);
    
    data.m_pCordIdList.swap(this->m_pCordIdList);
    data.m_pTextCoordsIdList.swap(this->m_pTextCoordsIdList);
    data.m_pColorsIdList.swap(this->m_pColorsIdList);
    data.m_pMatIdsList.swap(this->m_pMatIdsList);
    data.m_pVertices.swap(this->m_pVertices);
    data.m_pNormals.swap(this->m_pNormals);
    data.m_pColors.swap(this->m_pColors);
    data.m_pTexCoords.swap(this->m_pTexCoords);

    this->m_pModelData.push_back(data);
    
    this->m_pCordIdList.clear();
    this->m_pTextCoordsIdList.clear();
    this->m_pColorsIdList.clear();
    this->m_pMatIdsList.clear();
    this->m_pVertices.clear();
    this->m_pNormals.clear();
    this->m_pColors.clear();
    this->m_pTexCoords.clear();
    
    data.m_pCordIdList.clear();
    data.m_pTextCoordsIdList.clear();
    data.m_pColorsIdList.clear();
    data.m_pMatIdsList.clear();
    data.m_pVertices.clear();
    data.m_pNormals.clear();
    data.m_pColors.clear();
    data.m_pTexCoords.clear();
}